use serde::Serialize;
use sqlx::Row;
use sqlx::{Executor, SqliteConnection};

use crate::error::{SERVICE_NO_USER, SERVICE_PWD_INVALID};
use crate::{
    config::{ROOT_ACCOUNT, ROOT_NAME, ROOT_PWD},
    error::{AppErr, SqlxErr},
    utils::pwd_md5_enc,
};

use super::get_pool;

const CREATE_SQL: &'static str = r#"
CREATE TABLE IF NOT EXISTS tb_user (
    id INTEGER PRIMARY KEY AUTOINCREMENT, 
    name TEXT NOT NULL,
    account TEXT NOT NULL, 
    pwd_enc TEXT NOT NULL,
    UNIQUE(account)
)
"#;

#[derive(Debug, Serialize)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub account: String,
    pub pwd_enc: String,
}

pub async fn do_login(account: &str, pwd: &str) -> Result<User, AppErr> {
    let row = sqlx::query(
        r#"
        SELECT id, name, account, pwd_enc FROM tb_user WHERE account = ? LIMIT 1
    "#,
    )
    .bind(account)
    .fetch_optional(get_pool())
    .await?
    .ok_or(SERVICE_NO_USER.into_err())?;

    let user = User {
        id: row.get(0),
        name: row.get(1),
        account: row.get(2),
        pwd_enc: row.get(3),
    };
    let pwd_enc = pwd_md5_enc(pwd);
    if user.pwd_enc != pwd_enc {
        return Err(SERVICE_PWD_INVALID.into_err());
    }

    Ok(user)
}

async fn is_exists(conn: &mut SqliteConnection, account: &str) -> Result<bool, SqlxErr> {
    let row = sqlx::query(
        r#"
        SELECT id FROM tb_user WHERE account = ? LIMIT 1
    "#,
    )
    .bind(account)
    .fetch_optional(conn)
    .await?;

    Ok(row.is_some())
}

pub async fn init() -> Result<(), SqlxErr> {
    let pool = get_pool();

    pool.execute(CREATE_SQL).await?;

    let mut tx = pool.begin().await?;

    if !is_exists(&mut tx, ROOT_ACCOUNT).await? {
        create_user(&mut tx, ROOT_ACCOUNT, ROOT_PWD, ROOT_NAME).await?;
    }
    tx.commit().await?;

    Ok(())
}

async fn create_user(
    conn: &mut SqliteConnection,
    account: &str,
    pwd: &str,
    name: &str,
) -> Result<i64, SqlxErr> {
    let row = sqlx::query(
        r#"
        INSERT INTO tb_user (name, account, pwd_enc) VALUES (?, ?, ?)
    "#,
    )
    .bind(name)
    .bind(account)
    .bind(&pwd_md5_enc(pwd))
    .execute(conn)
    .await?;

    Ok(row.last_insert_rowid())
}
